﻿using CQRSFramework.Common.Serializer;
using CQRSFramework.Events;
using CQRSFramework.Events.Impl;
using Dapper;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CQRSFramework.MySql
{
    public class MySQLEventStore : IEventStore
    {
        private string _connectionString;
        private readonly IEventJsonSerializer _eventJsonSerializer;
        private readonly IJsonSerializer _jsonSerializer;
        public MySQLEventStore(IEventJsonSerializer eventJsonSerializer, IJsonSerializer jsonSerializer)
        {
            _eventJsonSerializer = eventJsonSerializer;
            _jsonSerializer = jsonSerializer;
        }
        public void InitEventStore(string connectionString)
        {
            _connectionString = connectionString;
        }
        public IEnumerable<DomainEventStream> Find(string aggregateRootId)
        {
            var sql = "select CommandId,AggregateRootId,AggregateRootTypeName,Version,Events,TimeStamp from eventstream where AggregateRootId=@aggregateRootId ";
            using (var conn = GetConnection())
            {
                var list = conn.Query<EventStream>(sql, new { AggregateRootId = aggregateRootId });
                return list.Select(e => new DomainEventStream
                {
                    CommandId = e.CommandId,
                    AggregateRootId = e.AggregateRootId,
                    AggregateRootTypeName = e.AggregateRootTypeName,
                    DomainEvents = _eventJsonSerializer.Deserialize(_jsonSerializer.Deserialize<IDictionary<string, string>>(e.Events)),
                    TimeStamp = e.TimeStamp,
                    Version = e.Version
                });
            }
        }
        public DomainEventStream Find(string aggregateRootId, string commandId)
        {
            DomainEventStream domainEventStream = null;
            var sql = "select CommandId,AggregateRootId,AggregateRootTypeName,Version,Events,TimeStamp from eventstream where AggregateRootId=@aggregateRootId and CommandId=@CommandId";
            using (var conn = GetConnection())
            {
                var result = conn.Query<EventStream>(sql, new { AggregateRootId = aggregateRootId, CommandId = commandId }).SingleOrDefault();
                if (result != null)
                {
                    domainEventStream = new DomainEventStream
                    {
                        CommandId = result.CommandId,
                        AggregateRootId = result.AggregateRootId,
                        AggregateRootTypeName = result.AggregateRootTypeName,
                        DomainEvents = _eventJsonSerializer.Deserialize(_jsonSerializer.Deserialize<IDictionary<string, string>>(result.Events)),
                        TimeStamp = result.TimeStamp,
                        Version = result.Version
                    };
                }
            }
            return domainEventStream;
        }
        public void Append(DomainEventStream eventStream)
        {
            var sql = "insert into eventstream (CommandId,AggregateRootId,AggregateRootTypeName,Version,Events,TimeStamp) values (@CommandId,@AggregateRootId,@AggregateRootTypeName,@Version,@Events,@TimeStamp)";
            using (var conn = GetConnection())
            {
                try
                {
                    conn.Execute(sql, new EventStream
                    {
                        CommandId = eventStream.CommandId,
                        AggregateRootId = eventStream.AggregateRootId,
                        AggregateRootTypeName = eventStream.AggregateRootTypeName,
                        Version = eventStream.Version,
                        Events = _jsonSerializer.Serialize(_eventJsonSerializer.Serialize(eventStream.DomainEvents)),
                        TimeStamp = DateTime.Now
                    });
                }
               
                catch (Exception ex)
                {
                    //throw new Exception(ex.Message);
                }
            }
        }
        public async Task<IEnumerable<DomainEventStream>> FindAsync(string aggregateRootId)
        {
            var sql = "select CommandId,AggregateRootId,AggregateRootTypeName,Version,Events,TimeStamp from eventstream where AggregateRootId=@aggregateRootId ";
            using (var conn = GetConnection())
            {
                var list = await conn.QueryAsync<EventStream>(sql, new { AggregateRootId = aggregateRootId });
                return list.Select(e => new DomainEventStream
                {
                    CommandId = e.CommandId,
                    AggregateRootId = e.AggregateRootId,
                    AggregateRootTypeName = e.AggregateRootTypeName,
                    DomainEvents = _eventJsonSerializer.Deserialize(_jsonSerializer.Deserialize<IDictionary<string, string>>(e.Events)),
                    TimeStamp = e.TimeStamp,
                    Version = e.Version
                });
            }
        }
        public async Task AppendAsync(DomainEventStream eventStream)
        {
            var sql = "insert into eventstream (CommandId,AggregateRootId,AggregateRootTypeName,Version,Events,TimeStamp) values (@CommandId,@AggregateRootId,@AggregateRootTypeName,@Version,@Events,@TimeStamp)";
            using (var conn = GetConnection())
            {
                try
                {
                    await conn.ExecuteAsync(sql, new EventStream
                    {
                        CommandId = eventStream.CommandId,
                        AggregateRootId = eventStream.AggregateRootId,
                        AggregateRootTypeName = eventStream.AggregateRootTypeName,
                        Version = eventStream.Version,
                        Events = _jsonSerializer.Serialize(_eventJsonSerializer.Serialize(eventStream.DomainEvents)),
                        TimeStamp = DateTime.Now
                    });
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
        }
        private MySqlConnection GetConnection()
        {
            try
            {
                return new MySqlConnection(_connectionString);
            }
            catch (MySqlException ex)
            {
                throw new Exception(ex.Message);
            }
        }
    }
    class EventStream
    {
        public string CommandId { get; set; }
        public string AggregateRootId { get; set; }
        public string AggregateRootTypeName { get; set; }
        public string Events { get; set; }
        public int Version { get; set; }
        public DateTime TimeStamp { get; set; }
    }
}
